﻿using System;
using System.Collections.Generic;
using Moq;
using NUnit.Framework;

namespace PoolKit.Tests
{
    [TestFixture]
    public class ResourceCollectionUsageTrackingTests
    {
        private static Mock<IStopwatchFactory> CreateStopwatchFactoryMock(TimeSpan idleTimeToSetForAllResources)
        {
            var stopwatchFactoryMock = new Mock<IStopwatchFactory>();

            var stopwatchMock = new Mock<IStopwatch>();
            stopwatchMock.SetupGet(x => x.Elapsed).Returns(idleTimeToSetForAllResources);

            stopwatchFactoryMock.Setup(x => x.Create()).Returns(stopwatchMock.Object);
            return stopwatchFactoryMock;
        }

        [Test]
        public void MakeSureCollectionReportsThatNoResourceIsIdleForADayWhenItStartsWithThemFromScratch()
        {
            var startingPoolOfResources = new[]{"string1", "string2", "string3"};

            var resourceCollection = new ResourceCollection<string>(startingPoolOfResources);

            var resourceCollectionUsageTracking =
                new ResourceCollectionUsageTracking<string>(resourceCollection, startingPoolOfResources, new StopwatchFactory());

            var resourcesThatHaventBeenTouchedOverADay =
                resourceCollectionUsageTracking.GetResourcesSittingIdle(TimeSpan.FromDays(1));

            CollectionAssert.IsEmpty(resourcesThatHaventBeenTouchedOverADay);
        }

        [Test]
        public void MakeSureCollectionReportsThatAllResourceHaveBeenIdleForAtLeast500MillisecondsIfThatIsSo()
        {
            var startingPoolOfResources = new[] { "string1", "string2", "string3" };

            var resourceCollection = new ResourceCollection<string>(startingPoolOfResources);

            Mock<IStopwatchFactory> stopwatchFactoryMock = CreateStopwatchFactoryMock(TimeSpan.FromSeconds(25));

            var resourceCollectionUsageTracking =
                new ResourceCollectionUsageTracking<string>(resourceCollection, startingPoolOfResources, stopwatchFactoryMock.Object);

            var resourcesThatHaventBeenTouchedOverLastMillisecond =
                resourceCollectionUsageTracking.GetResourcesSittingIdle(TimeSpan.FromMilliseconds(500));

            CollectionAssert.AreEquivalent(startingPoolOfResources, resourcesThatHaventBeenTouchedOverLastMillisecond);
        }

        [Test]
        public void MakeSureCollectionDoesNotReportABorrowedResourceAsBeingIdle()
        {
            var startingPoolOfResources = new[] { "string1", "string2", "string3" };

            var resourceCollection = new ResourceCollection<string>(startingPoolOfResources);

            Mock<IStopwatchFactory> stopwatchFactoryMock = CreateStopwatchFactoryMock(TimeSpan.FromSeconds(25));

            var resourceCollectionUsageTracking =
                new ResourceCollectionUsageTracking<string>(resourceCollection, startingPoolOfResources, stopwatchFactoryMock.Object);

            string resource;
            Assert.IsTrue(resourceCollectionUsageTracking.TryTake(out resource));

            var resourcesThatHaventBeenTouchedOverLastMillisecond =
                resourceCollectionUsageTracking.GetResourcesSittingIdle(TimeSpan.FromMilliseconds(15));

            var poolWithoutBorrowedResource = new List<string>(startingPoolOfResources);
            poolWithoutBorrowedResource.Remove(resource);

            CollectionAssert.AreEquivalent(poolWithoutBorrowedResource, resourcesThatHaventBeenTouchedOverLastMillisecond);
        }

        [Test]
        public void MakeSureCollectionDoesNotReportAHeavilyUsedResourceAsBeingIdle()
        {
            var startingPoolOfResources = new List<string>(new[] { "string1", "string2", "string3" });

            var resourceCollection = new ResourceCollection<string>(startingPoolOfResources);

            var stopwatchFactoryMock = new Mock<IStopwatchFactory>();

            var longTimeStartedStopwatchMock = new Mock<IStopwatch>();
            longTimeStartedStopwatchMock.SetupGet(x => x.Elapsed).Returns(TimeSpan.FromDays(25));

            var recentlyStartedStopwatchMock = new Mock<IStopwatch>();
            recentlyStartedStopwatchMock.SetupGet(x => x.Elapsed).Returns(TimeSpan.FromDays(1));

            int stopwatchFactoryCallNumber = 0;

            stopwatchFactoryMock.Setup(x=>x.Create()).Returns(
                new Func<IStopwatch>(delegate()
                                         {
                                             stopwatchFactoryCallNumber++;

                                             if (stopwatchFactoryCallNumber <= startingPoolOfResources.Count)
                                             {
                                                 // first - disguise all resources as being very old
                                                 return longTimeStartedStopwatchMock.Object;
                                             }
                                             else
                                             {
                                                 // then say: the ones tossed around are younger than those that aren't
                                                 return recentlyStartedStopwatchMock.Object;
                                             }
                                         }));

            var resourceCollectionUsageTracking =
                new ResourceCollectionUsageTracking<string>(resourceCollection, startingPoolOfResources, stopwatchFactoryMock.Object);

            string resource;
            Assert.IsTrue(resourceCollectionUsageTracking.TryTake(out resource));

            var poolWithoutBorrowedResource = new List<string>(startingPoolOfResources);
            poolWithoutBorrowedResource.Remove(resource);

            resourceCollectionUsageTracking.AcceptBack(resource);

            CollectionAssert.AreEquivalent(
                startingPoolOfResources,
                resourceCollectionUsageTracking.GetResourcesSittingIdle(TimeSpan.FromHours(8)));

            CollectionAssert.AreEquivalent(
                poolWithoutBorrowedResource,
                resourceCollectionUsageTracking.GetResourcesSittingIdle(TimeSpan.FromDays(3)));
        }
    }
}